﻿using System;
using System.Collections.Generic;
using System.Web;
using Telerik.Web.UI.Widgets;
using System.IO;
using System.Xml;
using System.Text.RegularExpressions;
using FTP;
using System.Configuration;

/// <summary>
/// Summary description for FtpContentProvider
/// </summary>
public class FtpContentProvider : FileBrowserContentProvider
{
	private string _itemHandlerPath;

	// A virtual folder, where the ftp files can be cached
	// or the Path.GetTempFileName() method can be used instead
	private string _tempDirectory;
	private string _protocolAndServer;
	private string _userName;
	private string _password;
	private FTPclient _ftpConnection;

	public FTPclient FtpConnection
	{
		get
		{
			return this._ftpConnection;
		}
	}

	protected string ItemHandlerPath
	{
		get
		{
			return this._itemHandlerPath;
		}
	}

	public FtpContentProvider(HttpContext context, string[] searchPatterns, string[] viewPaths, string[] uploadPaths, string[] deletePaths, string selectedUrl, string selectedItemTag)
		:
		base(context, searchPatterns, viewPaths, uploadPaths, deletePaths, selectedUrl, selectedItemTag)
	{
		// Read the configuration first
		this.ReadConfiguration();

		if (!string.IsNullOrEmpty(_userName) // User name is provided
			&&
			!string.IsNullOrEmpty(_password) // Password is provided
			)
		{// Use them
			this._ftpConnection = new FTPclient(_protocolAndServer, _userName, _password);
		}
		else
		{// initialize FTPClient without user name and password
			this._ftpConnection = new FTPclient(_protocolAndServer);
		}

		this._ftpConnection.UsePassive = true;
		this._ftpConnection.KeepAlive = true;
	}

	private void ReadConfiguration()
	{
		_protocolAndServer = VirtualPathUtility.RemoveTrailingSlash(ConfigurationManager.AppSettings["FtpServerName"]);
        _userName = ConfigurationManager.AppSettings["FtpUserName"];
        _password = ConfigurationManager.AppSettings["FtpPassword"];
		_tempDirectory = Context.Server.MapPath(VirtualPathUtility.AppendTrailingSlash(ConfigurationManager.AppSettings["TempDirectoryPath"]));// Use the physical path
		_itemHandlerPath = ConfigurationManager.AppSettings["ItemHandlerPath"].Replace("~/", "");// Remove the starting "~/" 

	}

	public override DirectoryItem ResolveRootDirectoryAsTree(string path)
	{
		path = VirtualPathUtility.RemoveTrailingSlash(path);
		path = PathHelper.AddStartingSymbol(path, '/');// adds "/" to the beginning of the path Example: ROOT/FOLDER ==> /ROOT/FOLDER

		DirectoryItem[] subDirs = this.GetDirectories(path);
		DirectoryItem result = new DirectoryItem(PathHelper.GetDirectoryName(path),
													string.Empty,
													path,
													string.Empty,
													this.GetPermissions(path),
													new FileItem[] { },
													subDirs);

		return result;
	}

	public override DirectoryItem ResolveDirectory(string path)
	{
		FileItem[] childFiles = this.GetFiles(path);
		DirectoryItem result = new DirectoryItem(PathHelper.GetDirectoryName(path),
													string.Empty,
													path,
													string.Empty,
													GetPermissions(path),
													childFiles,
													new DirectoryItem[] { });
		return result;
	}

	public override string MoveDirectory(string sourcePath, string destPath)
	{
		// FtpCopyDirectory can return an error message
		string result = FtpCopyDirectory(sourcePath, destPath);

		if (result != string.Empty)
			return result;// Return the error message
		// Else 
		result = DeleteDirectory(sourcePath);
		return result;
	}

	public override string MoveFile(string sourcePath, string destPath)
	{
		sourcePath = PathHelper.AddStartingSymbol(sourcePath, '/');
		destPath = PathHelper.AddStartingSymbol(destPath, '/');

		// Check whether the file already exists in the destination folder ;
		if (FtpConnection.FtpFileExists(destPath))
		{// Yes the file exists :
			string message = string.Format("The file '{0}' already exists", destPath);
			return message;
		}

		// There is not a permission issue with the FileExplorer's permissions ==> Move can be performed
		// But, there can be some FtpFileSystem error 
		try
		{
			// use rename for moving files ;
			bool isSuccessful = FtpConnection.FtpRename(sourcePath, destPath);

			if (isSuccessful)
				return string.Empty;

			else
				return "Unhandled exeption occured in 'CopyFile' method";
		}
		catch (Exception ex)
		{
			return "Unhandled exeption occured in 'CopyFile' method";
		}
	}

	public override string DeleteDirectory(string targetPath)
	{
		targetPath = VirtualPathUtility.RemoveTrailingSlash(targetPath);
		targetPath = PathHelper.AddStartingSymbol(targetPath, '/');

		// First delete the files in the directory
		foreach (FileItem fItem in this.GetFiles(targetPath))
		{
			string err = DeleteFile(fItem.Path);
			if (err != string.Empty)
				return err;
		}

		// then recursively delete the sub dirtectories 
		foreach (DirectoryItem dirItem in this.GetDirectories(targetPath))
		{
			string err = DeleteDirectory(dirItem.Path);
			if (err != string.Empty)
				return err;
		}

		// Finally delete the current directory 
		bool isDeleted = FtpConnection.FtpDeleteDirectory(targetPath);
		if (isDeleted)
		{
			return string.Empty;
		}
		// else 
		return string.Format("The directory '{0}' cannot be deleted", targetPath);
	}

	public override string DeleteFile(string targetPath)
	{
		bool isDeleted = FtpConnection.FtpDelete(targetPath);

		if (isDeleted)
		{
			return string.Empty;
		}

		// else 
		return string.Format("The file '{0}' cannot be deleted", targetPath);
	}

	public override string CopyDirectory(string sourcePath, string destPath)
	{
		sourcePath = PathHelper.AddStartingSymbol(sourcePath, '/');
		destPath = PathHelper.AddStartingSymbol(destPath, '/');

		// The name of the folder that will be created
		string newFolderName = VirtualPathUtility.AppendTrailingSlash(destPath) + PathHelper.GetDirectoryName(sourcePath);

		// FileSystem.CopyDirectory can return a string that contains the error or an empty string ;
		string errorMessage = FtpCopyDirectory(sourcePath, VirtualPathUtility.AppendTrailingSlash(newFolderName));
		return errorMessage;
	}

	public override string CopyFile(string sourcePath, string destPath)
	{
		// Check whether the file already exists in the destination folder ;
		if (FtpConnection.FtpFileExists(destPath))
		{// Yes the file exists
			string message = string.Format("The file '{0}' already exists. Operation is canceled!", destPath);
			return message;
		}

		// There is not a permission issue with the FileExplorer's permissions ==> Copy can be performed
		try
		{
			// Get the file
			Stream fileStream = GetFile(sourcePath);

			if (fileStream != null)
			{
				destPath = PathHelper.AddStartingSymbol(destPath, '/');// Starting slash should be removed

				// Upload the file to the new location
				bool isSuccessful = FtpConnection.Upload(fileStream, destPath);

				if (isSuccessful)
					return string.Empty; // The operation is successful
				else
					return "Unhandled exeption occured in 'CopyFile' method";
			}
			else
				return "Unhandled exeption occured in 'CopyFile' method";
		}
		catch (Exception ex)
		{
			return "Unhandled exeption occured in 'CopyFile' method";
		}
	}

	public override string CreateDirectory(string targetPath, string name)
	{
		targetPath = VirtualPathUtility.AppendTrailingSlash(targetPath);
		targetPath = PathHelper.AddStartingSymbol(targetPath, '/');
		string newFolderPath = targetPath + name + "/";

		if (FtpConnection.FtpDirectoryExists(newFolderPath))
		{
			string error = string.Format("The directory '{0}' already exists", newFolderPath);
			return error;
		}

		// There is not a restriction with the FileExplorer's permissions ==> Create can be performed
		// but there can be some FtpFileSystems restrictions  
		bool isCreated = FtpConnection.FtpCreateDirectory(newFolderPath);
		if (isCreated)
			return string.Empty;// no error
		else
			return "Unhandled exeption occured in 'CreateDirectory' method";
	}

	public override bool CheckReadPermissions(string folderPath)
	{
		bool hasPermission = CheckPermissions(folderPath, PathPermissions.Read);
		return hasPermission;
	}

	public override bool CheckWritePermissions(string virtualTargetPath)
	{
		bool hasPermission = CheckPermissions(virtualTargetPath, PathPermissions.Upload);
		return hasPermission;
	}

	public override bool CheckDeletePermissions(string folderPath)
	{
		bool hasPermission = CheckPermissions(folderPath, PathPermissions.Delete);
		return hasPermission;
	}

	public override string StoreFile(Telerik.Web.UI.UploadedFile file, string path, string name, params string[] arguments)
	{
		path = PathHelper.AddStartingSymbol(path, '/');
		string realPath = path + file.GetName();

		try
		{
			bool isUploaded = FtpConnection.Upload(file.InputStream, realPath);
			if (isUploaded)
				return realPath;
			else
			{
				return null;

				// or throw an exception:
				// throw new ArgumentException("The new file was not uploaded!");
			}
		}
		catch (Exception )
		{
			return "Unhandled exeption occured in 'CreateDirectory' method";
		}
	}

	// This function is obsolete
	public override string StoreFile(HttpPostedFile file, string path, string name, params string[] arguments)
	{
		throw new NotFiniteNumberException();
	}

	public override string StoreBitmap(System.Drawing.Bitmap bitmap, string url, System.Drawing.Imaging.ImageFormat format)
	{
		string realPath = RemoveProtocolNameAndServerName(url);
		realPath = PathHelper.AddStartingSymbol(realPath, '/');
		MemoryStream memStream = new MemoryStream();
		bitmap.Save(memStream, format);
		try
		{

			bool isUploaded = FtpConnection.Upload(memStream, realPath);
			if (isUploaded)
				return realPath;
			else
			{
				return null;
				// Or throw new ArgumentException("The new file was not uploaded!");
			}
		}
		catch (Exception ex)
		{
			return "Unhandled exeption occured in 'CreateDirectory' method";
		}
	}

	public override string GetFileName(string url)
	{
		return Path.GetFileName(RemoveProtocolNameAndServerName(url));
	}

	public override Stream GetFile(string url)
	{
		string path = RemoveProtocolNameAndServerName(url);
		path = PathHelper.AddStartingSymbol(path, '/');

		while (!FtpConnection.FtpFileExists(path))
		{
			return null;
		}

		try
		{
			string pathToCachFile = _tempDirectory + VirtualPathUtility.GetFileName(path);
			bool isSuccessful = FtpConnection.Download(path, pathToCachFile, true);

			if (isSuccessful)
			{
				FileStream fileStream = File.OpenRead(pathToCachFile);
				byte[] fileContent = new byte[fileStream.Length];
				fileStream.Read(fileContent, 0, (int)fileStream.Length);
				MemoryStream memStream = new MemoryStream(fileContent);
				fileStream.Close();
				File.Delete(pathToCachFile);
				return memStream;
			}
			else
			{
				if (File.Exists(pathToCachFile))
					File.Delete(pathToCachFile);

				return null;
			}
		}
		catch (Exception ex)
		{
			return null;
		}
	}

	public override bool CanCreateDirectory
	{
		get
		{
			return true;
		}
	}

	public override string GetPath(string path)
	{
		// First add the '/' sign at the begining of the path in order to use the VirtualPathUtility.GetDirectory() method
		string pathWithTilde = PathHelper.AddStartingSymbol(path, '/');
		string virtualPath = VirtualPathUtility.GetDirectory(pathWithTilde);// gets the parent directory's path

		return virtualPath;
	}

	public override char PathSeparator
	{
		get
		{
			return '/';
		}
	}

	# region Helper functions


	private bool CheckPermissions(string folderPath, PathPermissions permToCheck)
	{
		//add a ending slash to the upload folder
		folderPath = folderPath.TrimEnd(PathSeparator) + PathSeparator;


		string[] pathsToCheck;
		if ((permToCheck & PathPermissions.Upload) != 0)
			pathsToCheck = UploadPaths;
		else if ((permToCheck & PathPermissions.Delete) != 0)
			pathsToCheck = DeletePaths;
		else
			pathsToCheck = ViewPaths;


		//Compare the 'folderPath' to all paths in the 'pathsToCheck' collection and check if it is a child or one of them.
		foreach (string pathToCheck in pathsToCheck)
		{
			if (!String.IsNullOrEmpty(pathToCheck) && folderPath.StartsWith(pathToCheck, StringComparison.OrdinalIgnoreCase))
			{
				// Remove trailing slash from the path
				string trimmedPath = pathToCheck.TrimEnd(PathSeparator);
				//if (trimmedPath.Length == 0)
				//{
				//    //Path contains only the Path separator ==> give permissions everywhere
				//    return true;
				//}
				if (folderPath.Length > trimmedPath.Length && folderPath[trimmedPath.Length] == PathSeparator)
				{
					return true;
				}
			}
		}
		return false;
	}

	/// <summary>
	/// Returns the permissions for the provided virtual folder or file path
	/// </summary>
	/// <param name="virtualPath">The path to check the permissions</param>
	/// <returns></returns>
	private PathPermissions GetPermissions(string virtualPath)
	{
		PathPermissions permission = PathPermissions.Read;
		permission = CheckWritePermissions(virtualPath) ? permission | PathPermissions.Upload : permission;
		permission = CheckDeletePermissions(virtualPath) ? permission | PathPermissions.Delete : permission;

		return permission;
	}

	/// <summary>
	/// Gets the child directories of the passed virtual directory on the FTP server
	/// </summary>
	/// <param name="parentDirectoryPath">Path on the FTP server. 
	/// Example: If the paths is somethisng like this:
	/// ftp://localhost/rootfolder/PathToFolder/SubFolder/
	/// The passed value is somethisng like this : /rootfolder/PathToFolder/SubFolder/ without the protocol and server name
	/// </param>
	/// <returns></returns>
	private DirectoryItem[] GetDirectories(string parentDirectoryPath)
	{
		parentDirectoryPath = VirtualPathUtility.RemoveTrailingSlash(parentDirectoryPath);
		parentDirectoryPath = PathHelper.AddStartingSymbol(parentDirectoryPath, '/');
		List<DirectoryItem> directoriesResult = new List<DirectoryItem>();// Contains the result

		try
		{
			FTP.FTPdirectory dir = FtpConnection.ListDirectoryDetail(parentDirectoryPath);
			FTP.FTPdirectory ftpDirectories = dir.GetDirectories();
			foreach (FTPfileInfo ftpDirInfo in ftpDirectories.ToArray())
			{

				// The files and subfolders are added respectively in the 
				// ResolveRootDirectory and ResolveRootDirectoryAsTree methods
				DirectoryItem dirItem = new DirectoryItem(ftpDirInfo.Filename,
															string.Empty,
															VirtualPathUtility.AppendTrailingSlash(ftpDirInfo.FullName),
															string.Empty, GetPermissions(ftpDirInfo.FullName),
															new FileItem[] { }, new DirectoryItem[] { });
				directoriesResult.Add(dirItem);
			}
		}
		catch (Exception ex)
		{// Directory is not found 
		}

		return directoriesResult.ToArray();
	}


	/// <summary>
	/// Gets the child files of the passed virtual directory
	/// </summary>
	/// <param name="parentDirectoryPath">Path on the FTP server 
	/// Example: If the paths is somethisng like this :
	/// ftp://localhost/rootfolder/PathToFolder/SubFolder/
	/// The passed value is somethisng like this : /rootfolder/PathToFolder/SubFolder/ without the protocol and server name
	/// </param>
	/// <returns></returns>
	private FileItem[] GetFiles(string parentDirectoryPath)
	{
		List<FileItem> filesResult = new List<FileItem>();
		parentDirectoryPath = VirtualPathUtility.RemoveTrailingSlash(parentDirectoryPath);
		parentDirectoryPath = PathHelper.AddStartingSymbol(parentDirectoryPath, '/');

		try
		{
			FTP.FTPdirectory dir = FtpConnection.ListDirectoryDetail(parentDirectoryPath);
			List<FTPfileInfo> filteredFiles = new List<FTPfileInfo>();

			foreach (string filter in this.SearchPatterns)
			{
				if (filter == "*.*")
				{
					FTP.FTPdirectory ftpFiles = dir.GetFiles("");
					filteredFiles.AddRange(ftpFiles.ToArray());
				}
				else
				{// The filters cannot contain wildcards. The filtering is performed only by file extension. See the FTP.FTPdirectory.GetFiles method
					FTP.FTPdirectory ftpDirectories = dir.GetFiles(filter);
					filteredFiles.AddRange(ftpDirectories.ToArray());
				}
			}

			foreach (FTPfileInfo ftpFileInfo in filteredFiles)
			{
				long size = ftpFileInfo.Size;
				string url = ItemHandlerPath + "?path=" + ftpFileInfo.FullName;

				FileItem fileItem = new FileItem(ftpFileInfo.Filename,
														ftpFileInfo.Extension,
														(int)(size),
														string.Empty,
														url,
														string.Empty,
														GetPermissions(ftpFileInfo.FullName));

				filesResult.Add(fileItem);
			}
		}
		catch (Exception)
		{// The parent directory is moved or deleted

		}

		return filesResult.ToArray();
	}

	/// <summary>
	/// Physicaly copies a directory on the FTP server
	/// </summary>
	/// <param name="sourcePath">Path to the source directory, without the protocol and server name, e.g /Directory</param>
	/// <param name="destPath">Path to the destination directory, without the protocol and server name, e.g /Directory</param>
	/// <returns></returns>
	private string FtpCopyDirectory(string sourcePath, string destPath)
	{
		sourcePath = PathHelper.AddStartingSymbol(sourcePath, '/');
		destPath = PathHelper.AddStartingSymbol(destPath, '/');

		// Check whether the folder already exists in the destination folder ;
		if (FtpConnection.FtpDirectoryExists(destPath))
		{// Yes the folder exists :
			string message = string.Format("The folder '{0}' already exists", destPath);
			return message;
		}

		// Check whether the source directory is parent of the destination directory ;
		if (PathHelper.IsParentOf(sourcePath, destPath))
		{
			string message = string.Format("The folder '{0}' is parent of the '{1} directory. Operation is canceled!", sourcePath, destPath);
			return message;
		}

		try
		{
			destPath = VirtualPathUtility.RemoveTrailingSlash(destPath);// The path should not have ending '/' symbol
			bool isSuccessful = FtpConnection.FtpCreateDirectory(VirtualPathUtility.RemoveTrailingSlash(destPath));
			if (!isSuccessful)
				return "Unhandled exeption occured in 'CopyDirectory' method";
			// Else 
			// Directory is created ==> copy the files
			FileItem[] sourceFiles = this.GetFiles(sourcePath);
			foreach (FileItem fileItem in sourceFiles)
			{
				string currentElementTarget = VirtualPathUtility.AppendTrailingSlash(destPath) +
															PathHelper.RemoveStartingSymbol(fileItem.Path.Replace(sourcePath, ""), '/');
				string err = CopyFile(fileItem.Path, currentElementTarget);
				if (err != string.Empty)
					return err;
			}// Files are copird


			// Copy the sub folders
			DirectoryItem[] sourceDirectories = this.GetDirectories(sourcePath);
			foreach (DirectoryItem dirItem in sourceDirectories)
			{
				string currentFolderTarget = VirtualPathUtility.AppendTrailingSlash(destPath) + dirItem.Name;
				string err = FtpCopyDirectory(dirItem.Path, currentFolderTarget);
				if (err != string.Empty)
					return err;
			}

			return string.Empty;
		}
		catch (Exception ex)
		{
			return "Unhandled exeption occured in 'CopyDirectory' method";
		}
	}

	# endregion
}

